"""
This file is part of GSBEdit. For copyright and licensing information, please
see gsbedit.py in the GSBEdit base directory.
"""


import wx
import string
import sys
import webbrowser

import app.module_config as mc
import ui_elements.display_panel as dp

import ui_elements.panel_tabset as pt


ME_TEXT_NAME = 3001
ME_BTN_NEW_MODULE = 3002
ME_BTN_SAVE_MODULE = 3003
ME_BTN_DELETE_MODULE = 3004
ME_DPS_CHANGE = 3005
ME_BTN_ADD_MODULE = 3006
ME_BTN_OPEN_FILE = 3007


def update_dps(values):
    try:
        interval = float(values['fire_interval'])
        damage = float(values['damage'])
    except:
        return 'Damage per second: -'
    
    rate_of_fire = 1000. / interval
    dps = rate_of_fire * damage
    return 'Damage per second: %.1f' % dps


class ModuleEditor(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        
        self.parent = parent
        
        self.configs = mc.ModuleConfig('data/modules.cfg')
        
        ship_sizes = ['Cruiser', 'Frigate', 'Fighter']
        
        basic1group = [ {'type':'text',        'desc':'Name',             'key':'guiname',    'tip':'The name of the module as presented to the player.'},
                        {'type':'big_text',    'desc':'Description',      'key':'description', 'tip':'The module description presented to the player. Usually includes brief background information.'},
                        {'type':'text',        'desc':'Internal name',    'key':'name', 'tip':'The unique name of the module used internally by the game engine, not seen by the player.'},
                        {'type':'choice', 'desc':'Target ship size', 'key':'size',       'conversion':{'CRUISER':'Cruiser', 'FRIGATE':'Frigate', 'FIGHTER':'Fighter'}, 'options':ship_sizes, 'tip':'The class of ship that the module will be usable for.'}]
        
        
        classes = ['Armour', 'Beam weapon', 'Bullet weapon', 'Camoflage shield', 'Carrier', 'Crew', 'ECM beam', 'ECM shield', 'ECM shock', 'Engine', 'Kinetic weapon', 'Lightning gun', 'Limpet launcher', 'Missile launcher', 'PD scanner', 'Plasma launcher', 'Point defense', 'Power', 'Repair', 'Shield', 'Smart bomb', 'Targetting booster', 'Target painter', 'Tractor beam']
        class_conv = {'SIM_ArmourModule':'Armour', 'SIM_BeamWeaponModule':'Beam weapon', 'SIM_BulletWeaponModule':'Bullet weapon', 'SIM_CamoflageShieldModule':'Camoflage shield', 'SIM_CarrierModule':'Carrier', 'SIM_CrewModule':'Crew', 'SIM_ECMBeamModule':'ECM beam', 'SIM_ECMShieldModule':'ECM shield', 'SIM_ECMShockModule':'ECM shock', 'SIM_EngineModule':'Engine', 'SIM_KineticWeapon':'Kinetic weapon', 'SIM_LightningGunModule':'Lightning gun', 'SIM_LimpetLauncher':'Limpet launcher', 'SIM_MissileModule':'Missile launcher', 'SIM_PDScannerModule':'PD scanner', 'SIM_PlasmaLauncherModule':'Plasma launcher', 'SIM_PointDefenseModule':'Point defense', 'SIM_PowerModule':'Power', 'SIM_RepairModule':'Repair', 'SIM_ShieldModule':'Shield', 'SIM_SmartBombModule':'Smart bomb', 'SIM_TargetBoosterModule':'Targetting booster', 'SIM_TargetPainterModule':'Target painter', 'SIM_TractorModule':'Tractor beam'}
        slottypes = ['Standard and turret', 'Turret only']
        slottypes_conv = {'STANDARD': 'Standard and turret', 'TURRET': 'Turret only'}
        categories = ['Weapons', 'Defenses', 'Engines', 'Other']
        categories_conv = {'WEAPONS':'Weapons', 'DEFENSES':'Defenses', 'ENGINES':'Engines', 'OTHER':'Other'}
        
        basic2group = [ {'type':'choice', 'desc':'Class name', 'key':'classname', 'options':classes, 'conversion':class_conv, 'tip':'The module type. The game engine will provide different behaviour for the module based on this.'},
                        {'type':'choice', 'desc':'Slot type',  'key':'slot_type', 'options':slottypes, 'conversion':slottypes_conv, 'tip':'The type of slot that the module will fit into.'},
                        {'type':'choice', 'desc':'Category', 'key':'category',  'options':categories, 'conversion':categories_conv, 'tip':'The group that the module belongs in (used on the ship build screen).'}]
        
        
        basic3group = [ {'type':'text', 'desc':'Cost', 'key':'cost', 'tip':'The cost to fit the module to a ship.'},
                        {'type':'text', 'desc':'Weight', 'key':'weight', 'tip':'The weight of the module.'},
                        {'type':'text', 'desc':'Hitpoints', 'key':'hitpoints', 'tip':'The amount the damage the module can sustain before being destroyed.'},
                        {'type':'text', 'desc':'Crew requirement', 'key':'crew_required', 'tip':'The number of crew required to operate the module.'},
                        {'type':'text', 'desc':'Power consumption', 'key':'powerconsumed', 'tip':'The amount of power consumed by the module.'}]
        
        enginegroup = [{'type':'text', 'desc':'Thrust', 'key':'thrust', 'tip':'The thrust provided by the engine.'}]
        armourgroup = [{'type':'text', 'desc':'Maximum damage absorption', 'key':'maxdamageabsorbable', 'tip':'The maximum amount of damage that the armour will absorb before being destroyed.'}]
        crewgroup = [{'type':'text', 'desc':'Crew supplied', 'key':'crewsupply', 'tip':'The number of crew members supplied by this module.'}]
        powergroup = [{'type':'text', 'desc':'Power output', 'key':'powerproduced', 'tip':'The amount of power generated by this module.'}]
        
        cloakgroup = [  {'type':'text', 'desc':'Max. cloak power', 'key':'max_cloak_power', 'tip':'Maximum power capacity for the cloak module.'},
                        {'type':'text', 'desc':'Recharge rate', 'key':'cloak_rechargerate', 'tip':'The rate at which the cloak module recharges while not in use. [units?]'},
                        {'type':'text', 'desc':'Power consumption', 'key':'cloak_consumptionrate', 'tip':'The rate at which the cloak module consumes power while in use. [units?]'}]
                        
        ecmgroup = [    {'type':'text', 'desc':'ECM strength', 'key':'ecm_strength', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                        {'type':'text', 'desc':'Shock duration', 'key':'shock_duration', 'tip':'Length of time ECM effect applies to target (in milliseconds).'}]
        
        pdscannergroup = [{'type':'text', 'desc':'Scanner accuracy', 'key':'scanner_accuracy', 'tip':'Chance of the module correctly detecting warheads vs. decoys.'}]
        
        repair_types = ['Armor']
        repair_conv = {'ARMOUR':'Armour'}
        
        repairgroup = [ {'type':'text', 'desc':'Peace interval', 'key':'peace_interval', 'tip':'Minimum duration of ship not being attacked for module to activate.'},
                        {'type':'text', 'desc':'Repair rate', 'key':'repair_rate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                        {'type':'text', 'desc':'Supplies', 'key':'maxrepairsupplies', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                        {'type':'choice', 'desc':'Repair type', 'key':'repair_type',  'options':repair_types, 'conversion':repair_conv, 'tip':'What will be repaired.'}]
        
        shieldgroup = [ {'type':'text', 'desc':'Shield points', 'key':'shieldpoints', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                        {'type':'text', 'desc':'Resistance', 'key':'resistance', 'tip':'Weapon shield penetration required in order to cause damage.'},
                        {'type':'text', 'desc':'Recharge rate', 'key':'recharge_rate', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}]
        
        targetboostergroup = [{'type':'text', 'desc':'Target boost', 'key':'targetboost', 'tip':'The speed boost applied to tracking speed of other modules. [add/mult?]'}]
        
        weapongroup1 = [    {'type':'text', 'desc':'Firing interval', 'key':'fire_interval', 'tip':'The time between shots (in milliseconds).'},
                            {'type':'text', 'desc':'Damage', 'key':'damage', 'tip':'The damage caused to the target\'s hull if shields and armour are penetrated.'},
                            {'type':'text', 'desc':'Armour penetration', 'key':'armour_penetration', 'tip':'The maximum armour value that the weapon can penetrate.'},
                            {'type':'text', 'desc':'Shield penetration', 'key':'shield_penetration', 'tip':'The maximum shield resistance that the weapon can penetrate.'}]
        weapongroup_attr = [{'type':'text', 'text':'Damage per second: -', 'update_func':(update_dps, ('fire_interval', 'damage'))}]
        weapongroup2 = [    {'type':'text', 'desc':'Tracking speed', 'key':'tracking_speed', 'tip':'The speed at which the turret can track targets.'},
                            {'type':'text', 'desc':'Minimum range', 'key':'min_range', 'tip':'The minimum range of the weapon.'},
                            {'type':'text', 'desc':'Optimum range', 'key':'optimum_range', 'tip':'The optimum range of the weapon.'},
                            {'type':'text', 'desc':'Maximum range', 'key':'max_range', 'tip':'The maximum range of the weapon.'},
                            {'type':'text', 'desc':'Range', 'key':'range', 'tip':'Maximum range for tractor beams.'},
                            {'type':'text', 'desc':'Salvo size', 'key':'salvo_size', 'tip':'The number of shots that are fired in one salvo.'},
                            {'type':'text', 'desc':'Salvo interval', 'key':'salvo_interval', 'tip':'The time between salvos (in milliseconds).'}]
        
        radiationgroup = [  {'type':'text', 'desc':'Payload type', 'key':'payload', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Radiation damage', 'key':'radiation_damage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Radiation decay', 'key':'radiation_decay', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Total damage', 'key':'total_damage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Damage duration', 'key':'damage_duration', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}]
        
        beamgroup = [   {'type':'text', 'desc':'Beam width', 'key':'beamwidth', 'tip':'Width of the beam (if you know what units, let Kemp know).'},
                        {'type':'text', 'desc':'Beam duration', 'key':'beam_duration', 'tip':'How long the beam is drawn on the screen (in milliseconds).'},
                        {'type':'text', 'desc':'Firing delay', 'key':'firedelay', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                        {'type':'text', 'desc':'Beam texture', 'key':'beam_texture', 'tip':'The texture file for the beam (dds format)'},
                        {'type':'yes_no', 'desc':'Has grain', 'key':'has_grain', 'tip':'Should the grain texture be drawn over the beam?'},
                        {'type':'text', 'desc':'Grain', 'key':'grain', 'tip':'Texture file used for beam grain (dds format).'},
                        {'type':'yes_no', 'desc':'Beams converge', 'key':'convergence', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}]
        
        plasma_styles = ['Standard', 'Fragmented', 'Custom']
        plasma_styles_conv = {'STANDARD':'Standard', 'FRAGMENTED':'Fragmented', 'CUSTOM':'Custom'}
        plasmagroup = [ {'type':'choice', 'desc':'Style', 'key':'style', 'options':plasma_styles, 'conversion':plasma_styles_conv, 'tip':'Plasma missile graphical style.'},
                        {'type':'text', 'desc':'Texture', 'key':'plasma_texture', 'tip':'Texture for "Custom" plasma style.'},
                        {'type':'text', 'desc':'Max. in flight', 'key':'maxsimultaneous', 'tip':'Maximum number of plasma missile in flight (from a single module). Maximum of four. Blank is one.'},
                        {'type':'text', 'desc':'Sprite size', 'key':'spritesize', 'tip':'Size to draw the plasma missile sprite.'}]
        
        
        
        tractorbeamgroup = [{'type':'text', 'desc':'Weight added', 'key':'beamweight', 'tip':'The weight added to the target.'},
                            {'type':'text', 'desc':'Power capacity', 'key':'beampower', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Power usage', 'key':'powerusage', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Recharge rate', 'key':'powerrecharge', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}]
        
        weaponuigroup = [   {'type':'text', 'desc':'Turret sprite', 'key':'turret_sprite', 'tip':'The turret sprite to use in the game.'},
                            {'type':'text', 'desc':'Turret size', 'key':'turretsize', 'tip':'The size of the turret in the game.'},
                            {'type':'text', 'desc':'Sound', 'key':'sound', 'tip':'The sound to play when the weapon fires. Multiple randomised sounds are supported as of GSB v1.56.'},
                            {'type':'text', 'desc':'Sound volume', 'key':'soundvolume', 'tip':'The volume at which to play the firing sound (0.0 to 1.0).'},
                            {'type':'text', 'desc':'Colour', 'key':'color', 'tip':'Between 0 and 8 (only used for bullet weapons).'},
                            {'type':'text', 'desc':'Blast texture', 'key':'blasttexture', 'tip':'## NOT DONE - moan at Kemp if you see this ##'},
                            {'type':'text', 'desc':'Texture', 'key':'texture', 'tip':'## NOT DONE - moan at Kemp if you see this ##'}]

        miscgroup = [{'type':'text', 'desc':'Stack effectiveness', 'key':'stack_effectiveness', 'tip':'The performance multiplier when more than one module of this type is used. Should be less than 1.0 in most cases. Values above 1.0 are supported as of GSB v1.56.'},
                     {'type':'yes_no', 'desc':'Locked', 'key':'lockable', 'tip':'Does the player need to unlock the module?'},
                     {'type':'text', 'desc':'Unlock cost', 'key':'unlockcost', 'tip':'Cost in HQ points to unlock the module.'},
                     {'type':'text', 'desc':'Race restriction', 'key':'restricted', 'tip':'List of races the module is available to. Blank means all.'},
                     {'type':'yes_no', 'desc':'Hidden', 'key':'hidden', 'tip':'Should this module be hidden from the player (for AI/prefab-only modules).'}]
        
        basic2panel_note = [{'type' : 'text', 'text' : 'Values on this page are automatically determined. This will usually provide the appropriate choices.'}]
        
        basic1panel = [{'type' : 'attributes', 'controls' : [basic1group]}]
        basic2panel = [{'type' : 'attributes', 'controls' : [basic2group]}] #[{'type' : 'additional', 'items' : [basic2panel_note]}, {'type' : 'attributes', 'controls' : [basic2group]}]
        basic3panel = [{'type' : 'attributes', 'controls' : [basic3group]}]
        enginepanel = [{'type' : 'attributes', 'controls' : [enginegroup]}]
        armourpanel = [{'type' : 'attributes', 'controls' : [armourgroup]}]
        crewpanel = [{'type' : 'attributes', 'controls' : [crewgroup]}]
        powerpanel = [{'type' : 'attributes', 'controls' : [powergroup]}]
        cloakpanel = [{'type' : 'attributes', 'controls' : [cloakgroup]}]
        ecmpanel = [{'type' : 'attributes', 'controls' : [ecmgroup]}]
        pdscannerpanel = [{'type' : 'attributes', 'controls' : [pdscannergroup]}]
        repairpanel = [{'type' : 'attributes', 'controls' : [repairgroup]}]
        shieldpanel = [{'type' : 'attributes', 'controls' : [shieldgroup]}]
        targetboosterpanel = [{'type' : 'attributes', 'controls' : [targetboostergroup]}]
        weaponpanel = [{'type' : 'attributes', 'controls' : [weapongroup1, weapongroup2]}, {'type' : 'spacer'}, {'type' : 'additional', 'items' : [weapongroup_attr]}]
        radiationpanel = [{'type' : 'attributes', 'controls' : [radiationgroup]}]
        beampanel = [{'type' : 'attributes', 'controls' : [beamgroup]}]
        plasmapanel = [{'type' : 'attributes', 'controls' : [plasmagroup]}]
        tractorbeampanel = [{'type' : 'attributes', 'controls' : [tractorbeamgroup]}]
        weaponuipanel = [{'type' : 'attributes', 'controls' : [weaponuigroup]}]
        miscpanel = [{'type' : 'attributes', 'controls' : [miscgroup]}]
        
        tab_desc = [{'name': 'Basic 1',         'panel': basic1panel},
                    {'name': 'Basic 2',         'panel': basic2panel},
                    {'name': 'Basic 3',         'panel': basic3panel},
                    {'name': 'Engine',          'panel': enginepanel},
                    {'name': 'Armour',          'panel': armourpanel},
                    {'name': 'Crew',            'panel': crewpanel},
                    {'name': 'Power',           'panel': powerpanel},
                    {'name': 'Cloak',           'panel': cloakpanel},
                    {'name': 'ECM',             'panel': ecmpanel},
                    {'name': 'PD Scanner',      'panel': pdscannerpanel},
                    {'name': 'Repair',          'panel': repairpanel},
                    {'name': 'Shield',          'panel': shieldpanel},
                    {'name': 'Target Booster',  'panel': targetboosterpanel},
                    {'name': 'Weapon',          'panel': weaponpanel},
                    {'name': 'Radiation',       'panel': radiationpanel},
                    {'name': 'Beam',            'panel': beampanel},
                    {'name': 'Plasma',          'panel': plasmapanel},
                    {'name': 'Tractor Beam',    'panel': tractorbeampanel},
                    {'name': 'Weapon UI',       'panel': weaponuipanel},
                    {'name': 'Misc',            'panel': miscpanel}]
        
        tabset = pt.PanelTabset(self, tab_desc, 3100)
        
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        
        header_sizer = wx.BoxSizer(wx.HORIZONTAL)
        font12bold = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.FONTWEIGHT_BOLD)
        self.header = wx.StaticText(self, -1, 'No module selected')
        self.header.SetFont(font12bold)
        self.file_btn = wx.Button(self, ME_BTN_OPEN_FILE, 'Open module file')
        self.file_btn.Enable(False)
        header_sizer.Add(self.header)
        header_sizer.Add((0, 0), 1, wx.EXPAND)
        header_sizer.Add(self.file_btn)
        
        buttons = wx.BoxSizer(wx.HORIZONTAL)
        buttons.Add(wx.BitmapButton(self, ME_BTN_ADD_MODULE, wx.Bitmap('img/add.png')), 0, wx.EXPAND)
        buttons.Add((32, 1), 0)
        self.save_button = wx.BitmapButton(self, ME_BTN_SAVE_MODULE, wx.Bitmap('img/save.png'))
        self.save_button.Enable(False)
        buttons.Add(self.save_button, 0, wx.EXPAND)
        self.delete_button = wx.BitmapButton(self, ME_BTN_DELETE_MODULE, wx.Bitmap('img/delete.png'))
        self.delete_button.Enable(False)
        buttons.Add(self.delete_button, 0, wx.EXPAND)
        
        self.tabs = tabset
        
        self.main_sizer.Add((1, 5), 0)
        self.main_sizer.Add(header_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10)
        self.main_sizer.Add(buttons, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 10)
        self.main_sizer.Add(self.tabs, 1, wx.EXPAND | wx.ALL, border = 10)

        self.SetSizer(self.main_sizer)
        
        self.Bind(wx.EVT_BUTTON, self.save_module_relay, id=ME_BTN_SAVE_MODULE)
        
        self.Bind(wx.EVT_BUTTON, self.add_module, id=ME_BTN_ADD_MODULE)
        self.Bind(wx.EVT_BUTTON, self.delete_module, id=ME_BTN_DELETE_MODULE)
        self.Bind(wx.EVT_BUTTON, self.open_file, id=ME_BTN_OPEN_FILE)
        
        self.configs = mc.ModuleConfig('data/modules.cfg')
    
    
    def open_file(self, event):
        filename = self.module.get_filename()
        webbrowser.open(filename)
        
        
    def reverse_module_type_lookup(self, classname):
        module_type = self.configs.reverse_lookup(classname)
        if module_type == 'unknown':
            return 'custom'
        return module_type
    
    
    def set_module(self, module):
        self.save_prompt()
        self.tabs.set_module({'required': [], 'optional': []}, {})
        
        if module == None:
            self.save_button.Enable(False)
            self.delete_button.Enable(False)
            self.file_btn.Enable(False)
            return
        
        self.save_button.Enable()
        self.delete_button.Enable()
        self.file_btn.Enable()
            
        if module.has_value('gsbedit', 'module_type'):
            module_type = module.get_value('gsbedit', 'module_type')
        else:
            module_type = module.get_value('config', 'classname')
            module_type = self.reverse_module_type_lookup(module_type)
        
        module_config = self.configs.get_config(module_type)
    
        values = module.get_values('config')
        self.tabs.set_module(module_config, values)
        
        self.header.SetLabel(values['guiname'])
        
        self.module = module
        
    
    def add_module(self, event):
        self.save_prompt()
        self.parent.add_module()
    
    
    def delete_module(self, event):
        if self.parent.delete_module():
            self.set_module(None)
    
    
    def save_prompt(self):
        if self.tabs.have_values_changed():
            save_dlg = wx.MessageDialog(self, 'Do you wish to save your changes to this module?', 'Save changes?', 
    style=wx.YES_NO | wx.CENTRE | wx.ICON_QUESTION)
            result = save_dlg.ShowModal()
            if result == wx.ID_YES:
                self.save_module()
    
    
    def save_module_relay(self, event):
        self.save_module()
    
    def save_module(self):
        edits = self.tabs.get_changed_values()
        self.module.update_values('config', edits)
        self.module.write_to_file()
        self.tabs.save_values()
        self.parent.refresh_tree()
    
    def evt_value_updated(self, key, value, sender=None):
        pass
    
    def closing(self):
        self.save_prompt()


class ModuleEditorWindow(wx.Frame):
    def __init__(self, parent, module):
        wx.Frame.__init__(self, parent, -1, 'Module editor - %s' % module.get_name(), size=(600, 500))
        
        self.parent = parent        
        self.module = module
        
        self.Bind(wx.EVT_CLOSE, self.close)
        
        self.panel = ModuleEditor(self)
        self.panel.set_module(module)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.panel, 1, wx.EXPAND)

        self.SetSizer(sizer)
        
        self.Center()
        self.Show(True)
        self.Raise()
    
    
    def close(self, event):
        self.panel.save_prompt()
        self.Destroy()
        
